#include <ntstatus.h>
#define WIN32_NO_STATUS

#include "minicon.h"
#include "miniopt.h"
#include <shellapi.h>
#include <winternl.h>
#include <stierr.h>

#pragma warning (push)
#pragma warning (disable : 4201)
#include <winioctl.h>
#pragma warning (pop)

extern int err_ret(int errcode, const char *msg, const WCHAR *name);
extern int rm(WCHAR ** args,int na, bool v, bool f, bool inter, bool rec);

//=============================================================================
//=============================================================================

#ifndef OBJ_CASE_INSENSITIVE
#define OBJ_CASE_INSENSITIVE    0x00000040L
typedef enum _OBJECT_INFORMATION_CLASS
{ ObjectBasicInformation=0, ObjectNameInformation=1/*,...*/} OBJECT_INFORMATION_CLASS;
#define InitializeObjectAttributes( p, n, a, r, s )  \
   (p)->Length = sizeof( OBJECT_ATTRIBUTES ),        \
   (p)->RootDirectory = r,                           \
   (p)->Attributes = a,                              \
   (p)->ObjectName = n,                              \
   (p)->SecurityDescriptor = s,                      \
   (p)->SecurityQualityOfService = NULL
#else
#define ObjectNameInformation (OBJECT_INFORMATION_CLASS)1
#endif

typedef struct _OBJECT_NAME_INFORMATION
{ UNICODE_STRING Name; } OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;

#define FileLinkInformation   (_FILE_INFORMATION_CLASS)11

typedef struct _FILE_LINK_RENAME_INFORMATION { // Info Classes 10 and 11
   BOOLEAN ReplaceIfExists;
   HANDLE RootDirectory;
   ULONG FileNameLength;
   WCHAR FileName[1];
} FILE_LINK_INFORMATION, *PFILE_LINK_INFORMATION, FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;

typedef enum {
   INVALID_PATH = 0,
   UNC_PATH,
   ABSOLUTE_DRIVE_PATH,
   RELATIVE_DRIVE_PATH,
   ABSOLUTE_PATH,
   RELATIVE_PATH,
   DEVICE_PATH,
   UNC_DOT_PATH
} DOS_PATHNAME_TYPE;

#define SYMBOLIC_LINK_QUERY (0x0001)

#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020
#define FILE_OPEN_FOR_BACKUP_INTENT  0x00004000

#define DeclareUnicodeStringC(name, string) \
   UNICODE_STRING name = {sizeof(string), sizeof(string), string}; \
   (*lpfnRtlInitUnicodeString)(&name, name.Buffer)
#define DeclareUnicodeString(name) \
   UNICODE_STRING name = {0, 0, NULL}
#define InitUnicodeStringFromPWSTR(name, string) \
   UNICODE_STRING name = {0, 0, NULL}; \
   (*lpfnRtlInitUnicodeString)(&name, string)

//-----------------------------------------------------------------------------

#define DEF_FN_PTR(rtype, func, params)      \
   typedef rtype (WINAPI *TFN##func) params; \
   static TFN##func lpfn##func = NULL

#define GET_FN_ADDR(func, hmodule)           \
   lpfn##func = (TFN##func)::GetProcAddress(hmodule, #func)

DEF_FN_PTR(BOOL, CreateHardLinkW, (
   IN LPCWSTR lpFileName,
   IN LPCWSTR lpExistingFileName,
   IN LPSECURITY_ATTRIBUTES lpSecurityAttributes
));

#ifndef _WIN64
DEF_FN_PTR(BOOLEAN, RtlCreateUnicodeStringFromAsciiz, (
   OUT PUNICODE_STRING Destination, IN PCSTR Source
));
DEF_FN_PTR(NTSTATUS, ZwClose, (
   IN HANDLE Handle
));
DEF_FN_PTR(NTSTATUS, ZwSetInformationFile, (
   IN HANDLE Filehandle,
   OUT PIO_STATUS_BLOCK IoStatusBlock,
   IN PVOID FileInformation,
   IN ULONG FileInformationLength,
   IN FILE_INFORMATION_CLASS FileInformationClass
));
DEF_FN_PTR(BOOLEAN, RtlPrefixUnicodeString, (
   PUNICODE_STRING Prefix,
   PUNICODE_STRING ContainingString,
   BOOLEAN CaseInsensitive
));
DEF_FN_PTR(NTSTATUS, ZwOpenSymbolicLinkObject, (
   OUT PHANDLE SymbolicLinkHandle,
   IN ACCESS_MASK DesiredAccess,
   IN POBJECT_ATTRIBUTES ObjectAttributes
));
DEF_FN_PTR(NTSTATUS, ZwQuerySymbolicLinkObject, (
   IN HANDLE SymbolicLinkHandle,
   IN OUT PUNICODE_STRING TargetName,
   OUT PULONG ReturnLength OPTIONAL
));
DEF_FN_PTR(NTSTATUS, ZwOpenFile, (
   OUT PHANDLE FileHandle,
   IN ACCESS_MASK DesiredAccess,
   IN POBJECT_ATTRIBUTES ObjectAttributes,
   OUT PIO_STATUS_BLOCK IoStatusBlock,
   IN ULONG ShareAccess,
   IN ULONG OpenOptions
));
DEF_FN_PTR(PVOID, RtlAllocateHeap, (
   HANDLE Heap, ULONG Flags, ULONG	Size
));
DEF_FN_PTR(BOOLEAN, RtlFreeHeap, (
   HANDLE Heap, ULONG Flags, PVOID Address
));
DEF_FN_PTR(BOOLEAN, RtlDosPathNameToNtPathName_U, (
	PCWSTR DosName,
	PUNICODE_STRING NtName,
	PWSTR *DosFilePath /*= NULL*/,
	PVOID NtFilePath /*= NULL*/// Some special structure, first member being UNICODE_STRING
));
DEF_FN_PTR(VOID, RtlInitUnicodeString, (
	PUNICODE_STRING DestinationString,
	PCWSTR SourceString
));
DEF_FN_PTR(ULONG, RtlDetermineDosPathNameType_U, (
	PCWSTR Path
));
DEF_FN_PTR(DWORD, RtlNtStatusToDosError, (
	NTSTATUS StatusCode
));
DEF_FN_PTR(BOOLEAN, RtlCreateUnicodeString, (
	OUT PUNICODE_STRING Destination,
	IN PWSTR Source
));
DEF_FN_PTR(VOID, RtlFreeUnicodeString, (
	IN PUNICODE_STRING UnicodeString
));

__declspec(naked) inline HANDLE NtpGetProcessHeap()
{
   __asm{
      mov     eax, fs:0x30
      mov     eax, [eax+0x18]
      retn
   }
}

//-----------------------------------------------------------------------------

static BOOL WINAPI nt4_CreateHardLinkW (
   LPCWSTR szLinkName, LPCWSTR szLinkTarget, LPSECURITY_ATTRIBUTES
){
	DeclareUnicodeString(usNtName_LinkTarget);
	DeclareUnicodeString(usNtName_LinkName);
	DeclareUnicodeString(usSymLinkDrive);
	DeclareUnicodeStringC(usCheckDrive, L"\\??\\C:\0");
	DeclareUnicodeStringC(usLanMan, L"\\Device\\LanmanRedirector\\\0");
	HANDLE hHeap = NtpGetProcessHeap();
	DWORD dwFullPath, dwSize;
	NTSTATUS Status;
	PWSTR lpwszFullPath = NULL, lpwszFullPathFilePart = NULL;
	HANDLE hLinkTarget = NULL, hDrive = NULL;
	OBJECT_ATTRIBUTES oaMisc;
	IO_STATUS_BLOCK iostats;
	BOOL result = FALSE;
   PFILE_LINK_INFORMATION lpFileLinkInfo;

	if ((!szLinkName) || (!szLinkTarget))
   { ::SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
	
	if (((*lpfnRtlDetermineDosPathNameType_U)(szLinkName)   == UNC_PATH)
    || ((*lpfnRtlDetermineDosPathNameType_U)(szLinkTarget) == UNC_PATH))
   { ::SetLastError(ERROR_INVALID_NAME); return FALSE; }


   if (!(*lpfnRtlDosPathNameToNtPathName_U)(szLinkTarget, &usNtName_LinkTarget, NULL, NULL))
   { ::SetLastError(ERROR_PATH_NOT_FOUND); return FALSE; }
   if ((dwFullPath = ::GetFullPathNameW(szLinkTarget, 0, NULL, NULL)) == 0)
   { goto z; }
   dwFullPath += 2;
   if ((lpwszFullPath = (PWSTR)(*lpfnRtlAllocateHeap)(hHeap, HEAP_ZERO_MEMORY, dwFullPath*sizeof(WCHAR))) == NULL)
   { ::SetLastError(ERROR_NOT_ENOUGH_MEMORY); goto z; }
   if (!GetFullPathNameW(szLinkTarget, dwFullPath, lpwszFullPath, &lpwszFullPathFilePart))
   { goto y; }

   usCheckDrive.Buffer[4] = lpwszFullPath[0];
   InitializeObjectAttributes(&oaMisc, &usCheckDrive, OBJ_CASE_INSENSITIVE, NULL, NULL);
   if (!NT_SUCCESS(Status = (*lpfnZwOpenSymbolicLinkObject)(&hDrive, SYMBOLIC_LINK_QUERY, &oaMisc)))
   { ::SetLastError((*lpfnRtlNtStatusToDosError)(Status)); goto y; }

   usSymLinkDrive.MaximumLength = MAX_PATH * sizeof(WCHAR);
   if ((usSymLinkDrive.Buffer = (PWSTR)(*lpfnRtlAllocateHeap)(hHeap,HEAP_ZERO_MEMORY,usSymLinkDrive.MaximumLength)) == NULL)
   { ::SetLastError(ERROR_NOT_ENOUGH_MEMORY); goto x; }

   if (!NT_SUCCESS(Status = (*lpfnZwQuerySymbolicLinkObject)(hDrive, &usSymLinkDrive, NULL)))
   { ::SetLastError((*lpfnRtlNtStatusToDosError)(Status)); goto w; }
   if ((*lpfnRtlPrefixUnicodeString)(&usLanMan, &usSymLinkDrive, TRUE))
   { ::SetLastError(ERROR_INVALID_NAME); goto w; }

   InitializeObjectAttributes(&oaMisc, &usNtName_LinkTarget, OBJ_CASE_INSENSITIVE, NULL, NULL);
   if (!NT_SUCCESS((*lpfnZwOpenFile)(&hLinkTarget,
      DELETE | SYNCHRONIZE,
      &oaMisc,
      &iostats,
      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
      FILE_FLAG_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT
   )))
   { ::SetLastError((*lpfnRtlNtStatusToDosError)(Status)); goto w; }

   if (!(*lpfnRtlDosPathNameToNtPathName_U)(szLinkName, &usNtName_LinkName, NULL, NULL))
   { ::SetLastError(ERROR_INVALID_NAME); goto v; }

   dwSize = sizeof(FILE_LINK_INFORMATION) + usNtName_LinkName.Length + sizeof(WCHAR);
   if ((lpFileLinkInfo = (PFILE_LINK_INFORMATION)(*lpfnRtlAllocateHeap)(hHeap, HEAP_ZERO_MEMORY, dwSize)) == NULL)
   { ::SetLastError(ERROR_NOT_ENOUGH_MEMORY); goto u; }

   lpFileLinkInfo->ReplaceIfExists = FALSE;
   lpFileLinkInfo->RootDirectory = NULL;
   lpFileLinkInfo->FileNameLength = usNtName_LinkName.Length;
   Win::bmov(lpFileLinkInfo->FileName, usNtName_LinkName.Buffer, lpFileLinkInfo->FileNameLength);
   Win::bset(&iostats, 0, sizeof(iostats));
   result = NT_SUCCESS(Status = (*lpfnZwSetInformationFile)(
      hLinkTarget, &iostats, lpFileLinkInfo, dwSize, FileLinkInformation)
   );
   if (!result)
     ::SetLastError((*lpfnRtlNtStatusToDosError)(Status));

   (*lpfnRtlFreeHeap)(hHeap, 0, lpFileLinkInfo);
u: (*lpfnRtlFreeHeap)(hHeap, 0, usNtName_LinkName.Buffer);
v: (*lpfnZwClose)(hLinkTarget);
w: (*lpfnRtlFreeHeap)(hHeap, 0, usSymLinkDrive.Buffer);
x: (*lpfnZwClose)(hDrive);
y: (*lpfnRtlFreeHeap)(hHeap, 0, lpwszFullPath);
z: (*lpfnRtlFreeHeap)(hHeap, 0, usNtName_LinkTarget.Buffer);
   return result;
}
#endif

//-----------------------------------------------------------------------------

BOOL hard_link( WCHAR *from, WCHAR *to )
{
   static bool hlink_init_done = false;
   if (!hlink_init_done)
   {
      GET_FN_ADDR(CreateHardLinkW, Win::hkern);
#ifndef _WIN64
      if (!lpfnCreateHardLinkW) {
         GET_FN_ADDR(RtlCreateUnicodeStringFromAsciiz, Win::hnt);
         GET_FN_ADDR(ZwClose, Win::hnt);
         GET_FN_ADDR(ZwSetInformationFile, Win::hnt);
         GET_FN_ADDR(RtlPrefixUnicodeString, Win::hnt);
         GET_FN_ADDR(ZwOpenSymbolicLinkObject, Win::hnt);
         GET_FN_ADDR(ZwQuerySymbolicLinkObject, Win::hnt);
         GET_FN_ADDR(ZwOpenFile, Win::hnt);
         GET_FN_ADDR(RtlAllocateHeap, Win::hnt);
         GET_FN_ADDR(RtlFreeHeap, Win::hnt);
         GET_FN_ADDR(RtlDosPathNameToNtPathName_U, Win::hnt);
         GET_FN_ADDR(RtlInitUnicodeString, Win::hnt);
         GET_FN_ADDR(RtlDetermineDosPathNameType_U, Win::hnt);
         GET_FN_ADDR(RtlNtStatusToDosError, Win::hnt);
         GET_FN_ADDR(RtlCreateUnicodeString, Win::hnt);
         GET_FN_ADDR(RtlFreeUnicodeString, Win::hnt);
         if (lpfnRtlCreateUnicodeStringFromAsciiz
          && lpfnZwClose
          && lpfnZwSetInformationFile
          && lpfnRtlPrefixUnicodeString
          && lpfnZwOpenSymbolicLinkObject
          && lpfnZwQuerySymbolicLinkObject
          && lpfnZwOpenFile
          && lpfnRtlAllocateHeap
          && lpfnRtlFreeHeap
          && lpfnRtlDosPathNameToNtPathName_U
          && lpfnRtlInitUnicodeString
          && lpfnRtlDetermineDosPathNameType_U
          && lpfnRtlNtStatusToDosError
          && lpfnRtlCreateUnicodeString
          && lpfnRtlFreeUnicodeString)
         {
            lpfnCreateHardLinkW = nt4_CreateHardLinkW;
         }
      }
#endif
      hlink_init_done = true;
   }

   if (lpfnCreateHardLinkW)
      return (*lpfnCreateHardLinkW)(to, from, NULL);

   ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
   return FALSE;
}

//=============================================================================

DEF_FN_PTR(BOOL, GetVolumeNameForVolumeMountPointW, (
   __in   LPCTSTR lpszVolumeMountPoint,
   __out  LPTSTR lpszVolumeName,
   __in   DWORD cchBufferLength
));
DEF_FN_PTR(BOOL, SetVolumeMountPointW, (
   __in  LPCTSTR lpszVolumeMountPoint,
   __in  LPCTSTR lpszVolumeName
));

static BOOL is_volume(const WCHAR *nm, WCHAR *vol=0, DWORD volsz=0)
{
   if (Opt::is_root(nm))
   {
      WCHAR r[8];
      if (!vol) return TRUE;
      r[0]=r[1]=r[3]=r[6] = L'\\'; r[2] = L'?'; r[4] = nm[0]; r[5] = nm[1]; r[7] = L'\0';
      return (*lpfnGetVolumeNameForVolumeMountPointW)(r, vol, volsz);
   }
   int len = ::lstrlenW(nm);
   if (len == 48 || len == 49)
   {
      if (0 == Win::bcmp(L"\\\\?\\Volume{", nm, 2*(4+6+1))
         && nm[47] == L'}'
         && (!nm[48] || Opt::is_slash(nm[48])))
      {
         if (vol) 
         { Win::bmov(vol, nm, 48*2); vol[48] = L'\\'; vol[49] = L'\0'; }
         return TRUE;
      }
   }
   
   return FALSE;
}

//-----------------------------------------------------------------------------

BOOL mount_point( WCHAR *from, WCHAR *to )
{
   GET_FN_ADDR(GetVolumeNameForVolumeMountPointW, Win::hkern);
   GET_FN_ADDR(SetVolumeMountPointW, Win::hkern);
   if (!lpfnGetVolumeNameForVolumeMountPointW || !lpfnSetVolumeMountPointW)
   { ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; }

   WCHAR mp[4+MAX_PATH], *nt, vol[MAX_PATH];
   BOOL res = (NULL == (nt = Opt::nt_name(to, mp))) ? FALSE : TRUE;
   
   if (res && !Win::exists(mp+4, FILE_ATTRIBUTE_DIRECTORY))
      res = ::CreateDirectoryW(mp+4, NULL);

   if (res)
      res = is_volume(from, vol, MAX_PATH);
   
   if (res) {
      ::lstrcatW(nt, L"\\");
      res = (*lpfnSetVolumeMountPointW)(nt, vol);
   }

   return res;
}

//=============================================================================

#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY
# define SYMBOLIC_LINK_FLAG_DIRECTORY 0x01
#endif

#ifndef IO_REPARSE_TAG_SYMLINK
# define IO_REPARSE_TAG_SYMLINK       (0xA000000CL)
#endif

#ifndef SYMLINK_FLAG_RELATIVE
# define SYMLINK_FLAG_RELATIVE        1
#endif

#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE
typedef struct _REPARSE_DATA_BUFFER
{
   ULONG ReparseTag;
   USHORT ReparseDataLength;
   USHORT Reserved;
   union
   {
      struct
      {
         USHORT SubstituteNameOffset;
         USHORT SubstituteNameLength;
         USHORT PrintNameOffset;
         USHORT PrintNameLength;
         ULONG Flags;
         WCHAR PathBuffer[1];
      }
      SymbolicLinkReparseBuffer;
      struct
      {
         USHORT SubstituteNameOffset;
         USHORT SubstituteNameLength;
         USHORT PrintNameOffset;
         USHORT PrintNameLength;
         WCHAR PathBuffer[1];
      }
      MountPointReparseBuffer;
      struct
      {
         UCHAR  DataBuffer[1];
      }
      GenericReparseBuffer;
   };
}
REPARSE_DATA_BUFFER,*PREPARSE_DATA_BUFFER;

#define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER,GenericReparseBuffer)
#endif

#ifndef IO_REPARSE_TAG_VALID_VALUES
#define IO_REPARSE_TAG_VALID_VALUES 0xF000FFFF
#endif

#ifndef IsReparseTagValid
#define IsReparseTagValid(_tag) (!((_tag)&~IO_REPARSE_TAG_VALID_VALUES)&&((_tag)>IO_REPARSE_TAG_RESERVED_RANGE))
#endif

//-----------------------------------------------------------------------------

static bool FillREPARSE_DATA_BUFFER (
    PREPARSE_DATA_BUFFER rdb,
    LPCWSTR PrintName,size_t PrintNameLength,
    LPCWSTR SubstituteName,size_t SubstituteNameLength )
{
   bool Result=false;
   rdb->Reserved=0;
   
   switch (rdb->ReparseTag)
   {
      case IO_REPARSE_TAG_MOUNT_POINT:
         rdb->MountPointReparseBuffer.SubstituteNameOffset=0;
         rdb->MountPointReparseBuffer.SubstituteNameLength=static_cast<WORD>(SubstituteNameLength*sizeof(wchar_t));
         rdb->MountPointReparseBuffer.PrintNameOffset=(WORD)(rdb->MountPointReparseBuffer.SubstituteNameLength+2);
         rdb->MountPointReparseBuffer.PrintNameLength=static_cast<WORD>(PrintNameLength*sizeof(wchar_t));
         rdb->ReparseDataLength=(WORD)(FIELD_OFFSET(REPARSE_DATA_BUFFER,MountPointReparseBuffer.PathBuffer)+rdb->MountPointReparseBuffer.PrintNameOffset+rdb->MountPointReparseBuffer.PrintNameLength+1*sizeof(wchar_t)-REPARSE_DATA_BUFFER_HEADER_SIZE);
      
         if (rdb->ReparseDataLength+REPARSE_DATA_BUFFER_HEADER_SIZE<=static_cast<USHORT>(MAXIMUM_REPARSE_DATA_BUFFER_SIZE/sizeof(wchar_t)))
         {
            Win::bmov(&rdb->MountPointReparseBuffer.PathBuffer[rdb->MountPointReparseBuffer.SubstituteNameOffset/sizeof(wchar_t)],SubstituteName,2*(SubstituteNameLength+1));
            Win::bmov(&rdb->MountPointReparseBuffer.PathBuffer[rdb->MountPointReparseBuffer.PrintNameOffset/sizeof(wchar_t)],PrintName,2*(PrintNameLength+1));
            Result=true;
         }
      break;

      case IO_REPARSE_TAG_SYMLINK:
         rdb->SymbolicLinkReparseBuffer.PrintNameOffset=0;
         rdb->SymbolicLinkReparseBuffer.PrintNameLength=static_cast<WORD>(PrintNameLength*sizeof(wchar_t));
         rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset=rdb->MountPointReparseBuffer.PrintNameLength;
         rdb->SymbolicLinkReparseBuffer.SubstituteNameLength=static_cast<WORD>(SubstituteNameLength*sizeof(wchar_t));
         rdb->ReparseDataLength=(WORD)(FIELD_OFFSET(REPARSE_DATA_BUFFER,SymbolicLinkReparseBuffer.PathBuffer)+rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset+rdb->SymbolicLinkReparseBuffer.SubstituteNameLength-REPARSE_DATA_BUFFER_HEADER_SIZE);
      
         if (rdb->ReparseDataLength+REPARSE_DATA_BUFFER_HEADER_SIZE<=static_cast<USHORT>(MAXIMUM_REPARSE_DATA_BUFFER_SIZE/sizeof(wchar_t)))
         {
            Win::bmov(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset/sizeof(wchar_t)],SubstituteName,2*SubstituteNameLength);
            Win::bmov(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(wchar_t)],PrintName,2*PrintNameLength);
            Result=true;
         }
      break;
   }
   
   return Result;
}

//-----------------------------------------------------------------------------

static bool SetREPARSE_DATA_BUFFER(WCHAR *Object, PREPARSE_DATA_BUFFER rdb)
{
   if (!IsReparseTagValid(rdb->ReparseTag))
      return false;

   DWORD nr, f = FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS;
   HANDLE ho = ::CreateFileW(Object, GENERIC_WRITE,0,NULL,OPEN_EXISTING,f,NULL);
   if (INVALID_HANDLE_VALUE == ho)
      return false;

   BOOL res = ::DeviceIoControl(
      ho, FSCTL_SET_REPARSE_POINT,
      rdb, rdb->ReparseDataLength + REPARSE_DATA_BUFFER_HEADER_SIZE,
      NULL, 0, &nr, NULL
   );
   ::CloseHandle(ho);

   return (res != 0);
}

//-----------------------------------------------------------------------------

static PREPARSE_DATA_BUFFER get_rdb()
{
   static LPBYTE s_rbuff = NULL;
   Win::alloc((void* &)s_rbuff, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
   return reinterpret_cast<PREPARSE_DATA_BUFFER>(s_rbuff);
}

BOOL reparse_point(WCHAR *from, WCHAR *to, char rpt, bool insist_symlink=false)
{
   int ck_os_vers = insist_symlink ? 0x501 : 0x600;
   if (rpt != 'j' && Win::os_version < ck_os_vers) {
      if (rpt == 'd')
         rpt = 'j';
      else
         { ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; }
   }
   
   PREPARSE_DATA_BUFFER rdb = get_rdb();
   if (!rdb)
      return FALSE;
      
   rdb->ReparseTag = rpt == 'j' ? IO_REPARSE_TAG_MOUNT_POINT : IO_REPARSE_TAG_SYMLINK;
   if (rpt != 'j') rdb->SymbolicLinkReparseBuffer.Flags = 0;

   if (rpt == 'f') {
      HANDLE fh = ::CreateFileW(to, 0, 0, NULL, CREATE_NEW, FILE_FLAG_BACKUP_SEMANTICS, NULL);
      if (INVALID_HANDLE_VALUE == fh)
         return FALSE;
      ::CloseHandle(fh);
   }
   else {
      if (!::CreateDirectoryW(to, NULL))
         return FALSE;
   }
   
   WCHAR target[MAX_PATH+4], *nt_t,*nt_tp, link[MAX_PATH+4], *nt_l;
   if (NULL == (nt_l = Opt::nt_name(to,link)))
      return FALSE;
   
   nt_t = nt_tp = from;
   bool abs_link = rpt == 'j' || (
      from && from[0] && (from[1] == L':' || (from[0] == L'\\' && from[1] == L'\\'))
   );
   if (abs_link)
      nt_tp = (nt_t = Opt::nt_name(from, target)) + 4, nt_t[1] = L'?';
   else
      if (rpt != 'j') rdb->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE;
   if (!nt_t)
      return FALSE;
   
   if (!FillREPARSE_DATA_BUFFER(rdb, nt_tp,::lstrlenW(nt_tp), nt_t,::lstrlenW(nt_t)))
   { ::SetLastError(ERROR_INSUFFICIENT_BUFFER); return FALSE; }

   if (!SetREPARSE_DATA_BUFFER(nt_l, rdb))
      return FALSE;

   return TRUE;
}

//-----------------------------------------------------------------------------

DEF_FN_PTR(BOOLEAN, CreateSymbolicLinkW, (
  __in  LPTSTR lpSymlinkFileName,
  __in  LPTSTR lpTargetFileName,
  __in  DWORD dwFlags
));

static bool slink_init_done = false;

BOOL symbolic_link( WCHAR *from, WCHAR *to, bool is_dir, bool insist_symlink )
{
   if (!slink_init_done) {
      GET_FN_ADDR(CreateSymbolicLinkW, Win::hkern);
      slink_init_done = true;
   }

   BOOL res;
   if ( lpfnCreateSymbolicLinkW )
      res = (BOOL)(*lpfnCreateSymbolicLinkW)(to, from, is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0);
   else
      res = reparse_point( from, to, is_dir ? 'd' : 'f', insist_symlink);

   return res;
}

//=============================================================================
//=============================================================================

DEF_FN_PTR(DWORD, GetFinalPathNameByHandleW, (
   __in   HANDLE hFile,
   __out  LPTSTR lpszFilePath,
   __in   DWORD cchFilePath,
   __in   DWORD dwFlags
));

static bool gfp_checked = false;

#ifndef VOLUME_NAME_GUID
# define VOLUME_NAME_DOS      0x0  //default  
# define VOLUME_NAME_GUID     0x1
# define VOLUME_NAME_NT       0x2
# define FILE_NAME_NORMALIZED 0x0  //default
# define FILE_NAME_OPENED     0x8
#endif

DEF_FN_PTR(NTSTATUS, NtQueryObject, (
   __in_opt   HANDLE Handle,
   __in       OBJECT_INFORMATION_CLASS ObjectInformationClass,
   __out_opt  PVOID ObjectInformation,
   __in       ULONG ObjectInformationLength,
   __out_opt  PULONG ReturnLength
));
DEF_FN_PTR(HANDLE, FindFirstVolumeW, (
   LPWSTR lpszVolumeName,   // output buffer
   DWORD cchBufferLength    // size of output buffer
));
DEF_FN_PTR(BOOL, FindNextVolumeW, (
   HANDLE hFindVolume,      // volume search handle
   LPWSTR lpszVolumeName,   // output buffer
   DWORD cchBufferLength    // size of output buffer
));
DEF_FN_PTR(BOOL, FindVolumeClose, (
   HANDLE hFindVolume       // search handle
));
DEF_FN_PTR(BOOL, GetVolumePathNamesForVolumeNameW, (
   __in   LPCWSTR lpszVolumeName,
   __out  LPWSTR  lpszVolumePathNames,
   __in   DWORD   cchBufferLength,
   __out  PDWORD  lpcchReturnLength
));

static int ntf_checked = 0;

//-----------------------------------------------------------------------------

static int strcmpNW( const WCHAR *s1, const WCHAR *s2, int ms )
{
   int n1 = ::lstrlenW(s1);
   int n2 = ::lstrlenW(s2);
   if (n1 > ms) n1 = ms;
   if (n1 > n2) n1 = n2;
   return Win::bcmp(s1, s2, n1*sizeof(WCHAR));
}

//-----------------------------------------------------------------------------
#define mNameLen 512

bool real_filename(
   const WCHAR *nm, WCHAR *out, DWORD ms, bool norm, int must_exist )
{
   WCHAR fnm[MAX_PATH], *pw=0, c0 = 0, drive[64], *pdrive, *pd;
   struct obj_info {
      OBJECT_NAME_INFORMATION ni;
      WCHAR w1[mNameLen];
      WCHAR w2[mNameLen];
      WCHAR w3[mNameLen];
   } o;
   int nb, n0,tail, n1, n2;
   ULONG RetLen = 0;
   NTSTATUS res;
   HANDLE fh, hEnum = INVALID_HANDLE_VALUE;
   BOOL res1 = FALSE;

   if (!::GetFullPathNameW(nm, MAX_PATH, fnm, NULL))
      return false;
   out[ms-1] = L'\0'; ::lstrcpynW(out, fnm, (int)ms-1);
   //Con::f(L"nm=%s cb=%u norm=%d m_ex=%d\r\n", nm, ms, (int)norm, must_exist);
   n0 = tail = ::lstrlenW(fnm);
   for (;;) {
      fh = ::CreateFileW(fnm,
         0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
         OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL
      );
      //Con::f(L"fnm=%s fh=%p err=%u\r\n", fh, ::GetLastError());
      if (INVALID_HANDLE_VALUE != fh)
         break;
      if (must_exist)
         return false;

      fnm[n0] = c0; do { --n0; } while (n0 > 0 && fnm[n0-1] != L'\\');
      if (n0 <= 0)
         return false;

      c0 = fnm[n0]; fnm[n0] = L'\0';
   }
   fnm[n0] = c0; if (tail != n0) --n0; // starts from slash...

   if (!gfp_checked) {
      gfp_checked = true;
      GET_FN_ADDR(GetFinalPathNameByHandleW, Win::hkern);
   }
   if (norm && lpfnGetFinalPathNameByHandleW) {
      DWORD flg = FILE_NAME_OPENED | VOLUME_NAME_NT;
      if (norm) flg = FILE_NAME_NORMALIZED | VOLUME_NAME_DOS;
      DWORD dwr = (*lpfnGetFinalPathNameByHandleW)(fh, pw=o.w1,mNameLen, flg);
      if (dwr > mNameLen) ::SetLastError(ERROR_INSUFFICIENT_BUFFER);
      res1 = (dwr > 0 && dwr < mNameLen) ? TRUE : FALSE;
      RetLen = (ULONG)dwr;
      if (res1 && RetLen > 4 && Opt::is_ntname(pw))
      { pw += 4; RetLen -= 4; }
      goto e;
   }

   if (ntf_checked < 1) { GET_FN_ADDR(NtQueryObject,Win::hnt); ntf_checked=1; }
   if (!lpfnNtQueryObject){::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); goto e;}
 
   res = (*lpfnNtQueryObject)(fh,ObjectNameInformation,&o.ni,mNameLen,&RetLen);
   if (res == STATUS_BUFFER_OVERFLOW || res == STATUS_BUFFER_TOO_SMALL)
   { ::SetLastError(ERROR_INSUFFICIENT_BUFFER); goto e; }
   RetLen = nb = ::lstrlenW(pw = o.ni.Name.Buffer);
   res1 = TRUE;
   if (!norm) goto e;

   if (0 == strcmpNW(pw, L"\\Device\\LanmanRedirector", 24))
   { pw[0] = L'\\'; Win::bmov(pw+1, pw+24, 2*(RetLen-24+1)); goto e; }

   res1 = FALSE; RetLen = (ULONG)::GetLogicalDriveStringsW(mNameLen, o.w2);
   if (RetLen <= 0) goto e;
   if (RetLen > mNameLen){ ::SetLastError(ERROR_INSUFFICIENT_BUFFER); goto e; }

   pdrive = o.w2, pd = o.w3;
   drive[1] = L':'; drive[2] = L'\0';
   while (*pdrive) {
      drive[0] = *pdrive;
      RetLen = (ULONG)::QueryDosDeviceW(drive, pd, mNameLen);
      if (RetLen > 0 && RetLen <= mNameLen) {
         n1 = ::lstrlenW(pd);
         if (n1 < nb && 0 == strcmpNW(pw, pd, n1) && pw[n1] == L'\\') {
            pw[0] = drive[0]; pw[1] = drive[1];
            Win::bmov(pw+2, pw+n1, 2*(nb-n1+1));
            res1 = TRUE; RetLen = (ULONG)(2+nb-n1); goto e;
         }
      }
      pdrive += ::lstrlenW(pdrive) + 1;
   }

   if (ntf_checked < 2) {
      GET_FN_ADDR(FindFirstVolumeW, Win::hkern);
      GET_FN_ADDR(FindNextVolumeW,  Win::hkern);
      GET_FN_ADDR(FindVolumeClose,  Win::hkern);
      GET_FN_ADDR(GetVolumePathNamesForVolumeNameW, Win::hkern);
      ntf_checked = 2;
   }
   if (!lpfnFindFirstVolumeW || !lpfnFindNextVolumeW || !lpfnFindVolumeClose
    || !lpfnGetVolumePathNamesForVolumeNameW)
   goto e;
   
   hEnum = (*lpfnFindFirstVolumeW)(drive, 64);
   res1 = (hEnum == INVALID_HANDLE_VALUE) ? FALSE : TRUE;
   while (res1) {
      n1 = ::lstrlenW(drive);
      if (n1 >= 48) {
         if (drive[n1-1] == L'\\') drive[--n1] = L'\0';
         RetLen = (ULONG)::QueryDosDeviceW(drive+4, pd, mNameLen);
         if (RetLen > 0 && RetLen <= mNameLen) {
            drive[n1] = L'\\'; drive[n1+1] = L'\0';
            n1 = ::lstrlenW(pd);
            if (n1 < nb && 0 == strcmpNW(pw, pd, n1) && pw[n1] == L'\\') {
               if ((*lpfnGetVolumePathNamesForVolumeNameW)(
                  drive, pd, mNameLen, (PDWORD)&RetLen))
               {
                  n2 = ::lstrlenW(pd);
                  if (n2 > 1) {
                     if (pd[n2-1] == L'\\') pd[--n2] = L'\0';
                     Win::bmov(pw+n2, pw+n1, 2*(nb-n1+1));
                     Win::bmov(pw, pd, 2*n2);
                     RetLen = (ULONG)(n2 + nb-n1);
                     break;
                  }
               }
            }
         }
      }
      res1 = (*lpfnFindNextVolumeW)(hEnum, drive, 64);
   }
   
e: //--------------------------------------------------------------------------
   if (INVALID_HANDLE_VALUE != hEnum) (*lpfnFindVolumeClose)(hEnum);
   if (INVALID_HANDLE_VALUE != fh) ::CloseHandle(fh);
   if (TRUE != res1)
      return false;
   if (RetLen + tail-n0 + 1 > ms)   
   { ::SetLastError(ERROR_INSUFFICIENT_BUFFER); return false; }
   if (RetLen > 0) Win::bmov(out, pw, RetLen*2);
   if (tail > n0) Win::bmov(out+RetLen, fnm+n0, (tail-n0)*2);
   out[RetLen + tail-n0] = L'\0';
   return true;
}

//-----------------------------------------------------------------------------

static WCHAR up( WCHAR* &nm, WCHAR* &upname )
{
   static WCHAR dot[] = L".";
   WCHAR *pw = nm + ::lstrlenW(nm) - 1, cw = L'\0';
   while (pw >= nm && !Opt::is_slash(*pw) && *pw != L':') --pw;
   upname = ++pw;
   if ( pw > nm ) { cw = *pw; *pw = L'\0'; } else { nm = dot; }
   return cw;
}

int check_samefiles( const char *op, WCHAR *src_, WCHAR *dst, bool move=false )
{
   WCHAR f1[512], f2[512], *src=src_, sc=L'\0', *ps=NULL, dc=L'\0', *pd=NULL;
   bool diff;

   if (move) // one level up for real name
   { sc = up(src, ps); dc = up(dst, pd); }

   diff = (0 != ::lstrcmpiW(src, dst));
   if (diff) {
      if (real_filename(src, f1, 512, false, 1)
       && real_filename(dst, f2, 512, false, 1)
      )
      diff = (0 != ::lstrcmpiW(f1, f2));
   }
   if (sc) *ps = sc; if (dc) *pd = dc;

   if (diff || (move && 0 != ::lstrcmpW(ps, pd)))
      return 0;

   char ss[64];
   ::SetLastError(0);
   ::wsprintfA(ss, "%s to itself is not allowed for:", op);
   return err_ret(-17, ss, src_);
}

//-----------------------------------------------------------------------------

static bool print_rpt(const WCHAR *nm)
{
   PREPARSE_DATA_BUFFER rdb = get_rdb();
   if (NULL == rdb)
      return false;
   
   WCHAR *ntn, nt_n[MAX_PATH+4], fnm[MAX_PATH];
   if (!::GetFullPathNameW(nm, MAX_PATH, fnm, NULL))
      return false;
   if (NULL == (ntn = Opt::nt_name(fnm, nt_n)))
      return false;
   
   HANDLE fh = ::CreateFileW(ntn, 0,0,NULL, OPEN_EXISTING,
      FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
   if (INVALID_HANDLE_VALUE == fh)
      return false;

   DWORD dwr;
   BOOL res = ::DeviceIoControl(fh, FSCTL_GET_REPARSE_POINT, NULL,0, rdb, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dwr, NULL);
   ::CloseHandle(fh);
   if (!res || !IsReparseTagValid(rdb->ReparseTag))
      return false;

   int nw = 0;
   const char *rpt = "rpt";
   if (rdb->ReparseTag == IO_REPARSE_TAG_SYMLINK)
   {
      nw = (int)(rdb->SymbolicLinkReparseBuffer.PrintNameLength/sizeof(WCHAR));
      if (nw)
         ntn = rdb->SymbolicLinkReparseBuffer.PathBuffer + rdb->SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(WCHAR);
      else {
         nw = (int)(rdb->SymbolicLinkReparseBuffer.SubstituteNameLength/sizeof(WCHAR));
         ntn = rdb->SymbolicLinkReparseBuffer.PathBuffer + rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset/sizeof(WCHAR);
      }
   }
   else
   {
      rpt = "mnt";
      nw = (int)(rdb->MountPointReparseBuffer.PrintNameLength/sizeof(WCHAR));
      if (nw)
         ntn = rdb->MountPointReparseBuffer.PathBuffer + rdb->MountPointReparseBuffer.PrintNameOffset/sizeof(WCHAR);
      else {
         nw = (int)(rdb->MountPointReparseBuffer.SubstituteNameLength/sizeof(WCHAR));
         ntn = rdb->MountPointReparseBuffer.PathBuffer + rdb->MountPointReparseBuffer.SubstituteNameOffset/sizeof(WCHAR);
      }
   }
   if (nw <= 0)
      return false;

   Con::f(" %s=\"", rpt); Con::o(ntn, nw); Con::o('"');
   return true;
}

//---

static bool ck_hlink( const WIN32_FIND_DATAW *ff, Win::SearchContext *ctx )
{
   bool rpt = (0 != (ff->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT));
   ctx->ignore = rpt; // skip symbolic links

   if (ff->dwFileAttributes               != ctx->p1.fi->dwFileAttributes
    || ff->nFileSizeLow                   != ctx->p1.fi->nFileSizeLow
    || ff->nFileSizeHigh                  != ctx->p1.fi->nFileSizeHigh
    || ff->ftLastWriteTime.dwLowDateTime  != ctx->p1.fi->ftLastWriteTime.dwLowDateTime
    || ff->ftLastWriteTime.dwHighDateTime != ctx->p1.fi->ftLastWriteTime.dwHighDateTime
    || ff->ftCreationTime.dwLowDateTime   != ctx->p1.fi->ftCreationTime.dwLowDateTime
    || ff->ftCreationTime.dwHighDateTime  != ctx->p1.fi->ftCreationTime.dwHighDateTime
   )
   return false; // attributes mismatch

   if (ctx->cb_path <= ctx->nb_path + ::lstrlenW(ff->cFileName))
      return false; // too long name
   
   ::lstrcpyW(ctx->path + ctx->nb_path, ff->cFileName);
   if (0 == ::lstrcmpiW(ctx->path, ctx->p2.orig))
      return false; // skip original file

   HANDLE fh = ::CreateFileW(
      ctx->path,
      0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
      OPEN_EXISTING,
      FILE_FLAG_BACKUP_SEMANTICS | (rpt ? FILE_FLAG_OPEN_REPARSE_POINT : 0),
      NULL
   );
   if (INVALID_HANDLE_VALUE == fh)
      return false; // can't open file/directory/rpt

   BY_HANDLE_FILE_INFORMATION fi;
   BOOL res = ::GetFileInformationByHandle(fh, &fi);
   ::CloseHandle(fh);         
   if (!res)
      return false; // can't get file information

   if (ctx->p1.fi->dwVolumeSerialNumber != fi.dwVolumeSerialNumber) {
      ctx->done = true; // 
      return false;     // should never happen...
   }

   if (ctx->p1.fi->nFileIndexLow  != fi.nFileIndexLow
    || ctx->p1.fi->nFileIndexHigh != fi.nFileIndexHigh
   )
   return false; // file node mismatch

   ++ctx->found;
   Con::f("   %2u -- ", ctx->found); Con::o(ctx->path); Con::n();
   if (ctx->found+1 >= ctx->p1.fi->nNumberOfLinks)
      ctx->done = true; // all hard links found

   return true;
}

//---

static BOOL get_vol_path( const WCHAR *nm, bool rpt, WCHAR *vnm, DWORD cb )
{
   WCHAR fnm[MAX_PATH], cw, *pnm, *ptail=NULL;
   BOOL res = ::GetFullPathNameW(nm, MAX_PATH, pnm = fnm, NULL);
   if (res) {
      cw = L'\0';
      if (rpt) cw = up(pnm, ptail);

      static int gvp_checked = 0;
      DEF_FN_PTR(BOOL, GetVolumePathNameW, (
         __in LPCWSTR lpszFileName,
         __out_ecount(cchBufferLength) LPWSTR lpszVolumePathName,
         __in DWORD cchBufferLength
      ));
      if (!gvp_checked) {
         GET_FN_ADDR(GetVolumePathNameW, Win::hkern);
         gvp_checked = (NULL == lpfnGetVolumePathNameW) ? -1 : +1;
      }

      if (gvp_checked > 0) {
         res = (*lpfnGetVolumePathNameW)(pnm, vnm, cb);
      } else {
         if (!*pnm || pnm[1] != L':' || pnm[2] != L'\\') res = FALSE;
         else {
            vnm[0] = *pnm; vnm[1] = L':'; vnm[2] = L'\\'; vnm[3] = L'\0';
         }
      }

      if (cw) *ptail = cw;
   }
   return res;
}

//---

#pragma warning (disable : 4701) // potentially uninitialized variable fi
static int real( const WCHAR *srcn, int verbose, bool rpt )
{
   WCHAR dstn[512];

   if (verbose > 0)
   {
      Con::o(srcn); Con::n(); Con::o("  ");
      if (!real_filename(srcn, dstn, (DWORD)dim(dstn), false,1))
      { Con::o("failed"); Con::n(); return -31; }
      Con::o(dstn); Con::n(); Con::o("  ");
   }
   if (!real_filename(srcn, dstn, (DWORD)dim(dstn), true, 1))
   {
      Con::o("failed");
      if (!verbose) { Con::o(" -- "); Con::o(srcn); }
      Con::n();
      return -32;
   }

   if (verbose > 1)
   {
      BOOL res = FALSE;
      BY_HANDLE_FILE_INFORMATION fi;
      HANDLE fh = ::CreateFileW(rpt ? srcn : dstn,
         0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
         OPEN_EXISTING,
         FILE_FLAG_BACKUP_SEMANTICS | (rpt ? FILE_FLAG_OPEN_REPARSE_POINT : 0),
         NULL
      );
      if (INVALID_HANDLE_VALUE != fh )
      {
         res = ::GetFileInformationByHandle(fh, &fi);
         ::CloseHandle(fh);
      }
      if (res)
      {
         Con::o(dstn); Con::n();
         Con::f("  idx=%#x:%#x,%#x lnk=%u",
            fi.dwVolumeSerialNumber, fi.nFileIndexLow, fi.nFileIndexHigh, fi.nNumberOfLinks);
         if (rpt) print_rpt(srcn);
         Con::n();
         if (fi.nNumberOfLinks > 1)     // if other hard links exist
         {
            static int fff_checked = 0; // check if h-link enumeration API possible
            DEF_FN_PTR(HANDLE, FindFirstFileNameW, (
               __in     LPCWSTR lpFileName,
               __in     DWORD dwFlags,
               __inout  LPDWORD StringLength,
               __inout  PWCHAR LinkName
            ));
            DEF_FN_PTR(BOOL, FindNextFileNameW, (
               __in     HANDLE hFindStream,
               __inout  LPDWORD StringLength,
               __inout  PWCHAR LinkName
            ));
            if (!fff_checked) {
               GET_FN_ADDR(FindFirstFileNameW, Win::hkern);
               GET_FN_ADDR(FindNextFileNameW, Win::hkern);
               fff_checked = (NULL == lpfnFindNextFileNameW) ? -1 : +1;
            }
            WCHAR lnm[MAX_PATH];
            res = get_vol_path(srcn, rpt, lnm, MAX_PATH);
            if (res) {
               if (fff_checked > 0) {
                  DWORD n = 0, n1 = (DWORD)::lstrlenW(lnm) - 1, nl = MAX_PATH - n1;
                  HANDLE hf = (*lpfnFindFirstFileNameW)(dstn, 0, &nl, lnm+n1);
                  res = INVALID_HANDLE_VALUE != hf;
                  while (res) {
                     if (0 != ::lstrcmpiW(lnm, dstn))
                     { ++n; Con::f("   %2u -- ", n); Con::o(lnm); Con::n(); }
                     nl = MAX_PATH - n1;
                     res = (*lpfnFindNextFileNameW)(hf, &nl, lnm+n1);
                  }
                  if (INVALID_HANDLE_VALUE != hf) ::FindClose(hf);
               }
               else if (verbose > 2) {     // perform volume scan only if user persists...
                  WCHAR spath[MAX_PATH*2]; Win::SearchContext ctx;
                  Win::init_search_context(&ctx, spath,MAX_PATH*2, ck_hlink, &fi, dstn);
                  Win::search_files(&ctx, lnm, L"*", true);
               }
            }
         }
      }
      return 0;
   }

   Con::o(dstn); Con::n();
   return 0;
}
//#pragma warning (default : 4701) // potentially uninitialized variable fi

//=============================================================================
//=============================================================================

static char s_confirm = 'y';

static int link(
   WCHAR *src, WCHAR *dst, WIN32_FIND_DATAW *ff,
   int verbose, bool force, bool interactive, int l_type )
{
   WIN32_FIND_DATAW ff_d, *dff = NULL;
   HANDLE hFind = ::FindFirstFileW(dst, &ff_d);
   if ( INVALID_HANDLE_VALUE != hFind )
   {
      dff = &ff_d;
      ::FindClose(hFind);
   }

   if ( verbose )
   {
      Con::o("link"); Con::f(L" \"%s\" to \"%s\" -- ", src, dst);
   }

   if (dff)
   {
      int rc = check_samefiles("link", src, dst);
      if (0 != rc)
         return rc;
   }

   if ( interactive && dff )
   {
      if ( verbose ) Con::n();
      Con::o('"'); Con::o(dst);
      s_confirm = (char)Con::confirm("\" exists. Overwrite it", s_confirm);
      if ( s_confirm != 'y' && s_confirm != 'Y' )
      {
         return 0; // not confirmed
      }
   }

   if ( force && dff)
   {
      if (0 != (dff->dwFileAttributes & FILE_ATTRIBUTE_READONLY) )
      { ::SetFileAttributesW(dst, dff->dwFileAttributes & ~FILE_ATTRIBUTE_READONLY); }

      if (FILE_ATTRIBUTE_DIRECTORY != (dff->dwFileAttributes & (
          FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT))
      ) {
         ::DeleteFileW(dst);
      }
      else {
         //Too-o-o-o dangerous...
         //rm(&dst, 1, false, true, false, true);
      }
   }

   bool is_dir = 0 != (ff->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
   bool junction_preferred = is_dir && (l_type == 0);
   if (!l_type) l_type = is_dir ? +1:-1;

   BOOL res;
   if (l_type < 0) {
      res = hard_link(src, dst);
   }
   else {
      if (Win::os_version < 0x500) {
         ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
         return err_ret(-18, "Win2k or later required", NULL);
      }

      if (is_volume(src))
         res = mount_point(src, dst);
      else {
         if (junction_preferred)
            res = reparse_point(src, dst, 'j');
         else 
            res = symbolic_link(src, dst, is_dir, l_type > 1);
      }
   }

   if ( verbose )
   {
      Con::o(res ? "ok" : "failed"); Con::n();
   }

   int rc = 0;
   if ( !res )
   {
      rc = err_ret(-22, "Can't create link to", src);
   }

   return rc;
}

//=============================================================================

int link(
   char mode,
   WCHAR **args,int na, int verbose, bool force, bool interactive,int l_type )
{
   int i, rc;
   WCHAR srcn[512], dstn[512], *dst=NULL, *pw, last[2];

   if ('r' == mode && na < 1)
   { args[0] = L"."; na = 1; }

   s_confirm = 'y';
   bool dst_is_dir = false;

   if ( 'r' != mode  )
   {
      if (na < 2 )
          return Opt::opt_err(Opt::Err_Missed_Param);
   
      if (Opt::have_wildcard(dst = args[na-1]))
      { ::SetLastError(ERROR_INVALID_NAME); return err_ret(-20,"Invalid link name:",dst); }
   
      last[0] = Opt::last_slash_or_colon(dst); last[1] = L'\0';
      if (last[0]) {
         dst_is_dir = true; dst[::lstrlenW(dst)-1] = L'\0';
      }
      else {
         dst_is_dir = Win::exists(dst, FILE_ATTRIBUTE_DIRECTORY);
         if (dst_is_dir) last[0] = L'\\';
      }
   
      if ( !dst_is_dir && na > 2 )
         return Opt::opt_err(Opt::Err_TooMany_Params);
   }
   else {
      if (force) verbose += 2;
      if (l_type) ++verbose;
   }
   
   for ( rc = i = 0; i < ('r' == mode ? na : na - 1) && rc == 0; ++i )
   {
      if ('r' != mode && is_volume(args[i])) {
         if (l_type < 0) {
            ::SetLastError(0);
            return err_ret(-20, "Can't hardlink volume:", args[i]);
         }
         rc = link(args[i], args[na-1], NULL, verbose, force, interactive, +1);
         continue;
      }
      
      WIN32_FIND_DATAW ff;
      HANDLE hFind = ::FindFirstFileW(args[i], &ff);
      if ( INVALID_HANDLE_VALUE == hFind )
      {
         ::SetLastError(0);
         if (force && Opt::have_wildcard(args[i])) {
            if (verbose) { 
               Con::o("Source file(s) does not exist: "); Con::o(args[i]); Con::n();
            }
            continue;
         }
         else
            return err_ret(-21, "Source file(s) does not exist:", args[i]);
      }
      do 
      {
         if (Opt::is_dot(ff.cFileName))
            continue;

         if ('r' != mode) {
            if ( dst_is_dir )
            {
               ::lstrcpyW(dstn, dst);
               ::lstrcatW(dstn, last);
               ::lstrcatW(dstn,ff.cFileName);
            }
            else ::lstrcpyW(dstn, args[na-1]);
         }

         ::lstrcpyW(srcn, args[i]);
         pw = srcn + ::lstrlenW(srcn);
         while ( pw > srcn && pw[-1] != L'\\' && pw[-1] != L'/' && pw[-1] != L':') --pw;
         ::lstrcpyW(pw, ff.cFileName);

         int r1 = 'r' == mode
          ? real(srcn, verbose, 0 != (ff.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
          : link(srcn,dstn, &ff, verbose, force, interactive, l_type)
         ;
         if (!rc && r1) rc = r1;
         if ( (rc && !force) || ('r' != mode && !dst_is_dir) )
            break;
         
      } while ( ::FindNextFileW(hFind, &ff) );

      ::FindClose(hFind);
   }

   return rc;
}

//=============================================================================
//=============================================================================
